#include <graphics.h>          // 包含EGE的头文件
#include <cstdlib>
#include <ctime>
#include <queue>
#include <vector>
#include <algorithm>
#include <cmath>

using namespace std;

typedef struct Snake {
	int s[100][2]; // 100节最大； x y坐标
	int len;
} Snake;

int timecount = 0;
int fx = 1;
int fxs[4][2] = {{20, 0}, {0, 20}, {-20, 0}, {0, -20}}; // 方向
int food[2] = {310, 210}; // 食物x,y
int snakeDelay = 60; // 默认延迟值

typedef struct Node {
	int x, y;
	int dir; // 方向
	int g;   // 从起点到当前节点的实际代价
	int h;   // 从当前节点到终点的估计代价
	Node(int x, int y, int dir, int g, int h) : x(x), y(y), dir(dir), g(g), h(h) {}
	bool operator<(const Node& other) const {
		return (g + h) > (other.g + h); // 小顶堆
	}
} Node;

bool isValid(int x, int y, Snake *snake) {
	// 检查是否超出边界
	if (x < 0 || x >= 640 || y < 0 || y >= 640) return false;
	// 检查是否撞到蛇身
	for (int i = 0; i < snake->len; ++i) {
		if (snake->s[i][0] == x && snake->s[i][1] == y) return false;
	}
	return true;
}

int heuristic(int x1, int y1, int x2, int y2) {
	// 曼哈顿距离作为启发式函数
	return abs(x1 - x2) + abs(y1 - y2);
}

int astar(Snake *snake) {
	priority_queue<Node> pq;
	vector<vector<bool>> visited(640 / 20, vector<bool>(640 / 20, false));
	
	Node start = {snake->s[0][0], snake->s[0][1], fx, 0, heuristic(snake->s[0][0], snake->s[0][1], food[0], food[1])};
	pq.push(start);
	visited[start.x / 20][start.y / 20] = true;
	
	while (!pq.empty()) {
		Node curr = pq.top();
		pq.pop();
		
		if (curr.x == food[0] && curr.y == food[1]) {
			return curr.dir; // 找到路径
		}
		
		for (int dir = 0; dir < 4; ++dir) {
			int newX = curr.x + fxs[dir][0];
			int newY = curr.y + fxs[dir][1];
			
			if (isValid(newX, newY, snake) && !visited[newX / 20][newY / 20]) {
				visited[newX / 20][newY / 20] = true;
				int newG = curr.g + 1;
				int newH = heuristic(newX, newY, food[0], food[1]);
				pq.push(Node(newX, newY, dir, newG, newH));
			}
		}
	}
	
	return fx; // 如果找不到路径，保持当前方向
}

int autogo(Snake *snake) {
	if (food[0] == snake->s[0][0] || food[1] == snake->s[0][1]) {
		if (food[1] > snake->s[0][1]) {
			return 1; // 下
		} else if (food[1] < snake->s[0][1]) {
			return 3; // 上
		} else if (food[0] > snake->s[0][0]) {
			return 0; // 右
		} else if (food[0] < snake->s[0][0]) {
			return 2; // 左
		}
	}
	// 如果没有明确的方向，则返回当前方向
	return fx;
}

void autogosw(Snake *snake, bool autoMode, bool noWall) {
	if (autoMode) {
		fx = noWall ? autogo(snake) : astar(snake);
	} else {
		// 不自动寻路时，保持当前方向
	}
}

void keyevent(Snake *snakep, bool &autoMode, bool &noWall) {
	key_msg msgt;
	while (kbhit()) { // 在一个循环内进行按键的判断，比起用if判断按键的情况要好一些
		msgt = getkey(); // 获取按键信息
		int key = msgt.key; // 获取按键键值
		// 将大写字母转换为小写字母
		if (key >= 'A' && key <= 'Z') {
			key += ('a' - 'A');
		}
		if ((key == 'w' || key == VK_UP) && fx != 1)
			fx = 3;
		else if ((key == 's' || key == VK_DOWN) && fx != 3)
			fx = 1;
		else if ((key == 'd' || key == VK_RIGHT) && fx != 2)
			fx = 0;
		else if ((key == 'a' || key == VK_LEFT) && fx != 0)
			fx = 2;
		else if (key == 'k') {
			autoMode = true; // 开启自动寻路
		} else if (key == 'g') {
			autoMode = false; // 关闭自动寻路
		} else if (key == 'c') {
			noWall = true; // 开启穿墙模式
		} else if (key == 'q') {
			noWall = false; // 关闭穿墙模式
		} else if (key == '=') {
			snakeDelay = 120; // 加速
		} else if (key == '-') {
			snakeDelay = 60; // 减速
		}
	}
}
void move(Snake *snake, bool noWall = false) {
	// 移动蛇 调整蛇每个节点的xy坐标
	// 检查是否撞到墙壁
	if (!noWall) {
		if (snake->s[0][0] < 0 || snake->s[0][0] >= 640 || snake->s[0][1] < 0 || snake->s[0][1] >= 640) {
			// 撞到墙壁，游戏结束
			exit(0);
		}
	} else {
		// 对蛇头xy做限制，允许穿越边界
		snake->s[0][0] = (snake->s[0][0] + 640) % 640;
		snake->s[0][1] = (snake->s[0][1] + 640) % 640;
	}
	
	// 蛇身
	for (int j = snake->len - 1; j >= 1; j--) {
		snake->s[j][0] = snake->s[j - 1][0];
		snake->s[j][1] = snake->s[j - 1][1];
	}
	// 蛇头x y
	snake->s[0][0] += fxs[fx][0];
	snake->s[0][1] += fxs[fx][1];
}

void show(Snake *snake) {
	// 显示蛇
	setfillcolor(YELLOW);
	for (int i = 0; i < snake->len; i++) {
		int x = snake->s[i][0];
		int y = snake->s[i][1];
		// 显示
		fillellipse(x, y, 10, 10);
		setfillcolor(BLUE);
	}
}

void eat(Snake *snake, int xt, int yt) {
	// 吃食物
	if (food[0] == snake->s[0][0] && food[1] == snake->s[0][1]) {
		snake->len++;
		snake->s[snake->len - 1][0] = xt;
		snake->s[snake->len - 1][1] = yt;
		food[0] = (rand() % 32 * 20 + 10) % 640;
		food[1] = (rand() % 32 * 20 + 10) % 640;
	}
}

void showfood() {
	// 给颜色
	setcolor(EGERGB(0xFF, 0x0, 0x00)); // 设置绘画颜色为红色
	setfillcolor(EGERGB(0xFF, 0x0, 0x00)); // 设置填充色为红色
	
	// 显示食物
	fillellipse(food[0], food[1], 10, 10);
	// 颜色还原
	setcolor(EGERGB(0x0, 0x0, 0xFF)); // 设置绘画颜色为红色
	setfillcolor(EGERGB(0x0, 0x0, 0xFF)); // 设置填充色为红色
}

void showinf(Snake *snake) {
	// 显示程序
	// 颜色还原
	setcolor(EGERGB(0, 0xFF, 0));
	setbkmode(TRANSPARENT);
	settextjustify(CENTER_TEXT, CENTER_TEXT);
	xyprintf(320, 670, "食物x	=%-5d食物y=	%-5d蛇头x=	%-5d蛇头y=	%-5d蛇长=	%-5d",
		food[0], food[1], snake->s[0][0], snake->s[0][1], snake->len);
	// 颜色还原
	setcolor(EGERGB(0x0, 0x0, 0xFF)); // 设置绘画颜色为红色
}

Snake* snakeinit() {
	Snake *sp;
	sp = (Snake*)malloc(sizeof(Snake));
	sp->len = 3;
	sp->s[0][0] = 50;
	sp->s[0][1] = 110;
	
	sp->s[1][0] = 30;
	sp->s[1][1] = 110;
	
	sp->s[2][0] = 10;
	sp->s[2][1] = 110;
	return sp;
}

void showinit() {
	hide_console();
	initgraph(640, 700, INIT_RENDERMANUAL); // 初始化图形界面
	setrendermode(RENDER_MANUAL);
	setcaption("贪吃蛇"); // 设置窗口标题
	setbkcolor(WHITE); // 设置背景颜色为白色
	PIMAGE bg = newimage();
	getimage(bg, "微信图片_20230621190132_640x700.png");
	putimage(0, 0, 640, 700, bg, 0, 0, getwidth(bg), getheight(bg));
	setcolor(EGERGB(0x0, 0x0, 0xFF)); // 设置绘画颜色为红色
	setfillcolor(EGERGB(0x0, 0x0, 0xFF)); // 设置填充色为红色
}

int main() {
	int xt, yt;
	bool autoMode = false; // 控制自动寻路的开关，默认关闭
	bool noWall = false; // 控制作弊模式的开关，默认关闭
	
	Snake  *snakep;
	snakep = snakeinit();
	
	showinit();
	
	MUSIC bgMusic;
	bgMusic.OpenFile("羽肿 - 花火が瞬く夜に.mp3");
	bgMusic.SetVolume(1.0f);
	if (bgMusic.IsOpen()) {
		bgMusic.Play(0);
	}
//	RepeatPlay(bgMusic,)
	// 计时用，主要用来定时检查音乐播放
	int timeCount = 0;
	
	for (; is_run(); delay_fps(snakeDelay)) {
		// 隔1秒检查一下，如果播放完了，重新播放
		if ((++timeCount % 60 == 0) && (bgMusic.GetPlayStatus() == MUSIC_MODE_STOP)) {
			bgMusic.Play(0);
		}
		srand(time(0));
		if (timecount == 10) {
			timecount = 0;
			cleardevice(); // 清屏
			PIMAGE bg = newimage();
			getimage(bg, "微信图片_20230621190132_640x700.png");
			putimage(0, 0, bg);
			// 保存最后一个节点的xy 迟到食物增加蛇 节点的时候 使用
			xt = snakep->s[snakep->len - 1][0];
			yt = snakep->s[snakep->len - 1][1];
			
			move(snakep, noWall);
			
			show(snakep);
			
			eat(snakep, xt, yt);
			
			showfood();
			showinf(snakep);
			delimage(bg);
			
		}
		timecount++;
		keyevent(snakep, autoMode, noWall);
		autogosw(snakep, autoMode, noWall);
	}
	getch(); // 暂停，等待键盘按键
	bgMusic.Close();
	closegraph(); // 关闭图形界面
	return 0;
}
